﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading;

using NanoMvvm.Collections;
using NanoMvvm.ComponentModel;
using NanoMvvm.Threading;

namespace NanoMvvm
{
    public class TypeViewModel : PropertyObservableBase
    {
        private static readonly Dictionary<Type, DelegateKeyedDictionary<string, PropertyDescriptor>> registry;

        private static readonly ReaderWriterLockSlim propertyDescriptorsLock;

        static TypeViewModel()
        {
            propertyDescriptorsLock = new ReaderWriterLockSlim();
            registry = new Dictionary<Type, DelegateKeyedDictionary<string, PropertyDescriptor>>();
        }

        public TypeViewModel(Type type)
        {
            WrappedType = type;
            WrapAllProperties(type);
            StaticTypeDescriptionProvider.Register(this);
        }

        public Type WrappedType { get; private set; }

        public void RaisePropertiesChanged()
        {
            foreach (var descriptor in registry[WrappedType])
            {
                NotifyPropertyChanged(descriptor.Name);
            }
        }

        internal PropertyDescriptor[] GetWrappedProperties()
        {
            using (new ReadLock(propertyDescriptorsLock))
            {
                return registry[WrappedType].ToArray();
            }
        }

        internal PropertyDescriptor[] GetWrappedProperties(Attribute[] attributes)
        {
            using (new ReadLock(propertyDescriptorsLock))
            {
                return registry[WrappedType].Where(pd => pd.Attributes.OfType<Attribute>().Intersect(attributes).Any()).ToArray();
            }
        }

        private static void WrapAllProperties(Type type)
        {
            var props = type.GetProperties(BindingFlags.Static | BindingFlags.Public);

            using (new ReadLock(propertyDescriptorsLock))
            {
                DelegateKeyedDictionary<string, PropertyDescriptor> dict;
                if (!registry.TryGetValue(type, out dict))
                {
                    dict = new DelegateKeyedDictionary<string, PropertyDescriptor>(p => p.Name);
                    registry.Add(type, dict);
                }

                foreach (PropertyInfo prop in props)
                {
                    if (!dict.Contains(prop.Name))
                    {
                        dict.Add(WrapProperty(prop));
                    }
                }
            }
        }

        private static PropertyDescriptor WrapProperty(PropertyInfo prop)
        {
            if (prop == null)
            {
                throw new ArgumentNullException("prop");
            }

            var pd = new DelegatePropertyDescriptor(
                prop.DeclaringType,
                prop.PropertyType,
                prop.Name,
                PropertyHelper.CreateStaticGetter(prop),
                PropertyHelper.CreateStaticSetter(prop));

            return pd;
        }
    }
}